"""
当前在本地命名空间中的对象显示在主窗口左侧的侧边栏“对象”选项卡中。
对象选项卡是一个传统的对象调试器，您可以从web浏览器中的web检查器和相关开发工具中熟悉它。

在这个选项卡中，您可以更改对象或参数的值和关键字参数为任何函数，只需单击相关项并输入一个新值。

对象的方法列表包含在methods文件夹中。要运行一个方法，双击它，或右键单击并从选项菜单中选择“运行方法”。
如果该方法没有任何参数，则将运行它，否则将出现方法对话框。还可以从对象选项卡直接将方法拖拽到节点编辑器中。

要直接访问存储所有变量的名称和值的全局对象，请使用命令

>>> app.objects

有关可用属性的完整列表，请参阅类' objectree '。
"""
import doc, inspect
from util import vdict
from widget import Treeview, TreeEntry
from util import Color, argspec, classname, fontcolor, open_editor, BUTTON_RIGHT, BUTTON_RELEASE_RIGHT
from style import TREE_TAGS

class ObjectTree(vdict, Treeview):
	"""
	为了在视图之间同步当前内存中变量的值，
	曲尺将所有变量的名称和值存储在一个名为Object Tree的vdict中。
	vdict类似于普通的python字典，除了它有设置和删除项的回调功能。

	vdict将它的值存储在一个名为store的有序字典属性中。
	要显示对象树的当前内容，可以使用该命令

	>>> app.objects.store

	例如，如果您想设置变量' x '的值为' 1 '，则可以使用命令

	>>> x=1

	这等价于

	>>> app.objects["x"] = 1

	如果在向本地命名空间分配变量时需要细粒度控制，则可以使用函数

	>>> app.objects.setobj("x", 1)

	有关可用关键字参数的更多信息，请参阅' setobj '文档
	"""
	def __init__(self, app, *args, **kwargs):
		vdict.__init__(self, *args, setitem=self.setobj, delitem=self.delete)
		Treeview.__init__(self, app, *args, **kwargs)

		self.drag = None
		self.entry = TreeEntry(self)

		for i in TREE_TAGS:
			self.tag_configure(i, **TREE_TAGS[i])		

		self.tag_configure("class", foreground=Color.BLUE, font="Nunito 10")
		self.tag_configure("editable")
		self.tag_configure("argname", foreground=Color.DARK_ORANGE, font="Nunito 10")
		self.tag_configure("kwargname", foreground=Color.ORANGE, font="Nunito 10")
		
		self.bind("<Button-1>", self._on_button_1)
		self.bind("<Double-Button-1>", self._on_double_button_1)
		self.bind(BUTTON_RIGHT, self._on_button_right)
		self.bind(BUTTON_RELEASE_RIGHT, self._on_button_release_right)
		
	def setobj(self, name, value, create_new=False, coord=None, is_binop=False):
		"""
		每当创建新对象或对象的值发生变化时都会进行对象的回调。
		该方法负责更新在侧边栏的Objects选项卡中显示的值，并通知其他视图本地名称空间中变量的值发生了变化。

		参数
		----------
		name : string
		    变量名
		value : object
		    分配的值
		create_new : bool
			当设置为True时，如果已经有一个同名的键，则会生成一个唯一的变量名
		coord : tuple(float, float)
		    一个浮点数的元组,是节点编辑器中新创建的项的坐标


		示例
		--------
		>>> app.objects.setitem("x", create_new=True)
		>>> app.objects.setitem("x", create_new=True)
		"""
		name = self.unique_name(name) if create_new else name
		prev = self.app.node[name] if name in self.app.node else None
		order = self.order()
		expanded = self.expanded()
		update_value = prev and prev.argspec == argspec(value) and prev.classname == classname(value)
		is_output_item = prev in self.app.node.output.items or prev == self.app.node.output.log_item
		self.store[name] = value
		if is_output_item and not update_value:
			self.app.node.output.disconnect(prev)
			is_output_item = False
		self.app.node.setitem(name, update_value=update_value, coord=coord, is_output_item=is_output_item) 
		
		if update_value and self.exists(name + "<arg=<value>>"):
			self.item(name + "<arg=<value>>", 
				text=str(value), 
				tags=("editable", fontcolor(value, as_string=True)))
			return
		
		if self.exists(name):
			super(Treeview, self).delete(name)

		item = self.app.node[name]

		self.insert("", "end", name, text=name, tags=("object", "doc"))			
		self.insert(name, "end", name + "<class>", text=item.classname, tags=("class", "no_hover"))
		if not item.is_callable:
			self.insert(name, "end", name + "<arg=<value>>", text=str(value), tags=("editable", fontcolor(value, as_string=True)))

		if item.is_callable:
			for j in item.args:
				self.insert(name, "end", name + "<argname=" + j + ">", text=j, 
					tags=("argname", "no_hover"))
				self.insert(name, "end", name + "<arg=" + j + ">", 
					text=str(item.args[j]), 
					tags=("editable", fontcolor(item.args[j], as_string=True)))
			
			if len(item.kwargs) > 0:
				kwargs = self.insert(name, "end", name + "<kwargs>", text="kwargs")
				for k in item.kwargs:
					self.insert(kwargs, "end", name + "<argname=" + k + ">", text=k, 
						tags=("kwargname", "no_hover"))
					self.insert(kwargs, "end", name + "<arg=" + k + ">", 
						text=str(item.kwargs[k]), 
						tags=("editable", fontcolor(item.kwargs[k], as_string=True)))

		for j in dir(value):
			if j[:1] != "_":
				attr = getattr(value, j)
				if callable(attr):
					if not self.exists(name + "<methods>"):
						self.insert(name, "end", name + "<methods>", text="methods")
					self.insert(name + "<methods>", "end", name + "." + j, text=j, tags="doc")
				else:
					if not self.exists(name + "<constants>"):
						self.insert(name, "end", name + "<constants>", text="constants")
					self.insert(name + "<constants>", "end", name + "." + j, text=j + ": " + str(attr), tags="no_hover")

		self.order(order)
		self.expanded(expanded)
		
		if create_new:
			return name
		return False

	def delete(self, key):
		if key in self.app.node:
			item = self.app.node[key]
			item.disconnect()
			for i in item.args["connection"]:
				item.args["connection"][i].disconnect()
			for k in item.kwargs["connection"]:
				item.kwargs["connection"][k].disconnect()
		super(Treeview, self).delete(key)
		self.app.node[key].destroy()
	
	def unique_name(self, name):
	    i = 2
	    temp = name
	    while True:
	        if temp not in self:
	            return temp
	        temp = name + "_" + str(i)
	        i += 1

	def _on_button_1(self, event):
		key = self.identify_row(event.y)
		if not key: return

		if self.entry.editing:
			self.entry.finish(cancel=True)
		
		if self.has_tag(key, "editable"):			
			self.entry.edit(key, event)
			return "break"

		if self.has_tag(key, "no_hover"):
			return "break"

	def _on_double_button_1(self, event):
		key = self.identify_row(event.y)
		obj = help.getobj(key)
		if callable(obj) and "." in key:
			name, fn = key.rsplit(".", 1)
			self.app.node.run_method(fn, obj, self.app.node[name])

	def _on_button_right(self, event):
		key = self.identify_row(event.y)
		self.selection_set(key)
	
	def _on_button_release_right(self, event):
		key = self.identify_row(event.y)
		obj = help.getobj(key)
		if obj is None: return

		items = []

		if help.getobj(key):
			items.append({
				"label": "帮助",
				"command": lambda: help(key)
			})

		try:
			file = inspect.getsourcefile(self.app.objects[key])
			items.append({
				"label": "查看源代码",
				"command": lambda: open_editor(file)
			})
		except:
			pass

		if key in self.app.node and self.app.animate.can_animate(self.app.node[key]):
			items.append({
				"label": "动画",
				"command": lambda: self.app.animate(key)
			})

		if callable(obj) and "." in key:
			name, fn = key.rsplit(".", 1)
			items.append({
				"label": "运行方法",
				"command": lambda: self.app.node.run_method(fn, obj, self.app.node[name])
			})

		if key in self:
			items.extend([{
				"separator": None
			},{
				"label": "删除 " + key,
				"command": lambda: self.delete(key)
			}])

		self.menu.set_menu(items)
		self.menu.show(event)
